/*!
 *	\file myfs_namei.c
 *
 *	в данном файле хранятся функции для работы с элементами директории
 *
 */

#include <linux/fs.h>
#include "myfs.h"
#include "myfs_dir.h"
#include "myfs_inode.h"
#include "myfs_ialloc.h"

/*!
	функция добавления нового элемента в директортю
*/
static int myfs_add_dentry(struct dentry *dentry, struct inode *inode)
{
	int err = myfs_add_link(dentry, inode);
	if (!err)
	{
		d_instantiate(dentry, inode);
		return 0;
	}
	inode_dec_link_count(inode);
	iput(inode);
	return err;
}

/*!
	функция создания нового файла
*/
static int myfs_create(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd)
{
	struct inode *inode = myfs_new_inode(dir, mode);
	int err = PTR_ERR(inode);
	if (!IS_ERR(inode))
	{
		printk(KERN_ALERT "myfs: creating new file \"%s\" with inode %lu.\n", dentry->d_name.name, inode->i_ino);
		/*
		 * добавление структур с указателями на функции
		 */
		inode->i_op = &myfs_file_inode_operations;
		inode->i_fop = &myfs_file_operations;
		inode->i_mapping->a_ops = &myfs_aops;

		mark_inode_dirty(inode);

		/* добавляем inode в директорию  */
		err = myfs_add_dentry(dentry, inode);	
	}
	return err;
}

/*!
	функция используется при вычислении пути
*/
static struct dentry *myfs_lookup(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
	struct inode *inode = NULL;
	unsigned int ino;

	if(dentry->d_name.len > MYFS_MAX_NAME_LEN)
		return ERR_PTR(-ENAMETOOLONG);

	ino = myfs_inode_by_name(dir, &dentry->d_name);

	if (ino)
	{
		inode = myfs_iget(dir->i_sb, ino);
		if (IS_ERR(inode))
			return ERR_CAST(inode);
	}

	return d_splice_alias(inode, dentry);
}

/*!
	функция удаления элемента директории
*/
static int myfs_unlink(struct inode *dir, struct dentry *dentry)
{
	struct inode *inode = dentry->d_inode;
	struct myfs_dir_entry *de;
	struct page *page;
	int err = -ENOENT;

	printk(KERN_ALERT "myfs: deleting file \"%s\".\n", dentry->d_name.name);
	de = myfs_find_dentry(dir, &dentry->d_name, &page);
	if (!de)
	{
		printk(KERN_ALERT "myfs: unable to find requested filename.\n");
		goto out;
	}

	err = myfs_delete_entry(de, page);
	if (err)
	{
		goto out;
	}
	inode_dec_link_count(inode);
out:
	return err;
}

/*!
	функция удаления директории
*/
static int myfs_rmdir(struct inode * dir, struct dentry *dentry)
{
	struct inode * inode = dentry->d_inode;
	int err = -ENOTEMPTY;

	if (myfs_empty_dir(inode))
	{
		err = myfs_unlink(dir, dentry);
		if (!err)
		{
			inode_dec_link_count(dir);
			inode_dec_link_count(inode);
		}
	}
	return err;
}

/*!
	функция создания символьной ссылки
*/
static int myfs_symlink(struct inode *dir, struct dentry *dentry, const char* symname)
{
	int err;
	struct super_block *sb = dir->i_sb;
	int len = strlen(symname) + 1;
	struct inode *inode;
	if(len > sb->s_blocksize)
	{
		err = -ENAMETOOLONG;
		goto out;
	}
	inode = myfs_new_inode(dir, S_IFLNK | 0777);
	err = PTR_ERR(inode);
	if(IS_ERR(inode))
	{
		printk(KERN_ALERT "myfs: error creating new inode errno = %d.\n", err);
		goto out;
	}
	inode->i_op = &myfs_symlink_inode_operations;
	inode->i_mapping->a_ops = &myfs_aops;
	err = page_symlink(inode, symname, len);
	if(err)
	{
		goto out_fail;
	}
	err = myfs_add_dentry(dentry, inode);
out:
	return err;

out_fail:
	inode_dec_link_count(inode);
	iput(inode);
	return err;
}

/*!
	функция создания новой директории
*/
static int myfs_mkdir(struct inode * dir, struct dentry *dentry, int mode)
{
	struct inode * inode;
	int err = -EMLINK;

	inode_inc_link_count(dir);

	inode = myfs_new_inode(dir, S_IFDIR | mode);
	err = PTR_ERR(inode);
	if (IS_ERR(inode))
	{
		printk(KERN_ALERT "myfs: error creating new inode errno = %d.\n", err);
		goto out_dir;
	}

	myfs_set_inode(inode, 0);

	inode_inc_link_count(inode);

	err = myfs_make_empty(inode, dir);
	if (err)
		goto out_fail;

	err = myfs_add_link(dentry, inode);
	if (err)
		goto out_fail;

	d_instantiate(dentry, inode);
out:
	return err;

out_fail:
	inode_dec_link_count(inode);
	inode_dec_link_count(inode);
	iput(inode);
out_dir:
	inode_dec_link_count(dir);

	goto out;
}

/*!
	в данной структуре хранятся ссылки на функции для работы с элементами директории
*/
const struct inode_operations myfs_dir_inode_operations = {
	.create = myfs_create,
	.lookup = myfs_lookup,
	.symlink = myfs_symlink,
	.unlink = myfs_unlink,
	.mkdir = myfs_mkdir,
	.rmdir = myfs_rmdir,
	//.rename = myfs_rename,
	//.setattr = myfs_setattr,
	.permission = myfs_permission,
};
